/**
 * KopsoxFramework
 * Copyright (C) 2010 Free Software Foundation, Inc. <http://fsf.org/>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kopsox.data.impl;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;

import org.kopsox.comm.listener.Observed;
import org.kopsox.comm.listener.Observer;
import org.kopsox.comm.message.ErrorMessage;
import org.kopsox.comm.message.ValueMessage;
import org.kopsox.comm.message.impl.LoggingMessageImpl;
import org.kopsox.data.ValidateableValue;
import org.kopsox.data.Value;
import org.kopsox.data.ValueDescriptor;
import org.kopsox.environment.ValueEnvironment;
import org.kopsox.environment.proxy.KopsoxRegistry;
import org.kopsox.general.priority.LoggingPriorityEnum;
import org.kopsox.util.comm.ObserverTreeSet;
import org.kopsox.util.log.KopsoxLogger;
import org.kopsox.util.validation.ValidationUtil;
import org.kopsox.util.validation.ValidatorSet;
import org.kopsox.validation.Validator;

/**
 * @author Konrad Renner
 * @param <T> 
 * 
 */
public abstract class AbstractPolymorph<T extends Serializable> implements ValidateableValue<T>,
                Observed{
	private final static KopsoxLogger LOGGER  = KopsoxRegistry.INSTANCE.getKopsoxLoggerForClass(AbstractPolymorph.class);
	
    private static final long serialVersionUID = 2970397307706983211L;
    
    //Values
    private final T value;          //The actual Value
    private T backupValue;  //A copy of the 'old' state of this value
    
    private boolean isDirty;        //true if Value was created via changeValue
    private final ValueDescriptor descriptor;                       //Describes the properties of the value
    
    //Validation
    private final ValidatorSet validators;
    
    //Observers
    private final ObserverTreeSet observers;
    
    /**
     * This constructor loads the Descriptor, Validators, Observers, ... from the ValueEnvironment
     * @param value
     * @param name
     * @param namespace 
     */
    public AbstractPolymorph(T value,String name,String namespace){
        this.value = value;
        this.backupValue = null;
        this.isDirty = false;
            
        ValueEnvironment environment = KopsoxRegistry.INSTANCE.getValueEnvironment();
        
        ValueDescriptor desc = environment.getValueDescription(name, namespace);
        if(desc == null) {
        	
        	if(LOGGER.isLogEnabeld(LoggingPriorityEnum.ALL.getPriority())) {
        		LOGGER.writeLogMessage(new LoggingMessageImpl("Could not load ValueDescription from Environment","Constructor"));
        	}
        	
            this.descriptor = new ValueDescriptorImpl.ValueDescriptorBuilder(name).namespace(namespace).build();
            this.observers = new ObserverTreeSet();
            this.validators = ValidationUtil.createValidatorsFromValueDescription(this.descriptor);
        }else {
        	if(LOGGER.isLogEnabeld(LoggingPriorityEnum.ALL.getPriority())) {
        		LOGGER.writeLogMessage(new LoggingMessageImpl("Loaded ValueDescription from Environment","Constructor"));
        	}
        	
            this.descriptor = desc;
            this.validators = ValidationUtil.createValidatorsFromValueDescription(this.descriptor);
            ValidatorSet envVal = environment.getValidatorsForValue(name, namespace);
            if(envVal != null && envVal.size() > 0) {
            	this.validators.addAll(environment.getValidatorsForValue(name, namespace));
            }
            this.observers = environment.getObserversForValue(name, namespace);
        }
        
        //Enable Auto-Delegation for Validators
        this.validators.setAutomaticDelegationToHandlers(true);
    }
    
    public AbstractPolymorph(T value, ValueDescriptor desc){
        this.value = value;
        this.backupValue = null;
        this.isDirty = false;
        
        if(desc != null) {
                this.descriptor = desc;
        }else
                this.descriptor = new ValueDescriptorImpl.ValueDescriptorBuilder().build();
        
        //They should always be ordered! The validator or observer with the higher priority should be called first
        this.validators = ValidationUtil.createValidatorsFromValueDescription(this.descriptor);
        this.observers = new ObserverTreeSet();
        
        //Enable Auto-Delegation for Validators
        this.validators.setAutomaticDelegationToHandlers(true);
    }

    /* (non-Javadoc)
     * @see org.kopsox.validation.Validateable#validate()
     */
    @Override
    public boolean notifyValidators(ValueMessage message) {
        return this.validators.validateValueMessage(message);
    }
    

    @Override
    public List<ErrorMessage> getValidationErrors() {
        return this.validators.getValidationErrors();
    }

    /**
     * Gets the value
     * 
     * @return value
     */
    public T getValue(){
        return this.value;
    }
    
    public void notifyObservers(ValueMessage message){
         this.observers.notification(message);
    }
    
    public String toString(){
        return this.value.toString();
    }
    
    @Override
    public Collection<Validator> getAllValidators() {
        return this.validators;
    }

    @Override
    public Collection<Observer> getAllObservers() {
        return this.observers;
    }

    @Override
    public synchronized void addAllValidators(Collection<Validator> validator) {
        this.validators.addAll(validator);
    }

    @Override
    public synchronized boolean addAllObservers(Collection<Observer> observer) {
        return this.observers.addAll(observer);
    }

    /* (non-Javadoc)
     * @see org.kopsox.validation.Validateable#addValidator(org.kopsox.validation.Validator)
     */
    @Override
    public synchronized void addValidator(final Validator validator) {
        this.validators.add(validator);
    }
    
    /* (non-Javadoc)
     * @see org.kopsox.validation.Validateable#removeValidator(org.kopsox.validation.Validator)
     */
    @Override
    public synchronized void removeValidator(final Validator validator) {
        this.validators.remove(validator);
    }
    
    /* (non-Javadoc)
     * @see org.kopsox.comm.listener.Observed#addObserver(org.kopsox.comm.listener.Observer)
     */
    @Override
    public synchronized boolean addObserver(final Observer observer) {
        return this.observers.add(observer);
    }

    /* (non-Javadoc)
     * @see org.kopsox.comm.listener.Observed#removeObserver(org.kopsox.comm.listener.Observer)
     */
    @Override
    public synchronized boolean removeObserver(final Observer observer) {
        return this.observers.remove(observer);
    }
    
    /**
     * Gets the ValueDescriptor of this value.<br>
     * The ValueDescriptor holds many information of the value, e.g. the name of the value...
     * 
     * @return ValueDescriptor
     */
    public ValueDescriptor getValueDescriptor() {
        return this.descriptor;
    }
    

    /**
     * Gets the BackupValue
     */
    public T getValueBackup() {
        return this.backupValue;
    }
    
    protected void setValueBackup(final T backup){
        this.backupValue = backup;
    }

    /**
     * True if the value was created via the changeValue-Method
     * 
     * @return boolean
     */
    public boolean isDirty() {
        return this.isDirty;
    }
    
    /**
     * Sets the dirty-Flag
     * @param dirty
     */
    protected void setIsDirty(final boolean dirty){
        this.isDirty = dirty;
    }

    //Each implementation has to implement these very important methods
    public abstract int hashCode();
    public abstract boolean equals(Object object);
    
    
    @SuppressWarnings("unchecked")
    @Override
    public int compareTo(Value<T> o) {
        if(this.value instanceof java.util.Date) {
                java.util.Date asDate = (java.util.Date)this.value;
                long difference = asDate.getTime() - ((java.util.Date) o.getValue()).getTime();
                return (int)difference;
        }
        //Its very dirty...
        try {
                return ((Comparable) this.value).compareTo(o.getValue());
        }catch(ClassCastException cce) {
                return toString().compareTo(o.toString());
        }
    }
}
